﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;
using HeapSpecSupport;
using SimpleIR;

namespace Evaluator.EvalState
{
	internal sealed class GlobalState
	{
		/// <summary>
		/// The current call stack.
		/// </summary>
		private readonly List<LocalFrame> m_callstack;

		/// <summary>
		/// The global (static) fields in the programs.
		/// </summary>
		private readonly Dictionary<IRStaticField, ELocation> m_globals;

		[ContractInvariantMethod]
		private void ClassInvariant()
		{
			Contract.Invariant(this.m_callstack != null);
			Contract.Invariant(this.m_globals != null);
		}

		internal GlobalState()
		{
			this.m_callstack = new List<LocalFrame>();
			this.m_globals = new Dictionary<IRStaticField, ELocation>();
		}

		/// <summary>
		/// Push a new frame and load the arguments as needed for entering a method.
		/// </summary>
		internal void EnterMethodCall(IRBodyDef bdf, bool isconsinvoke, List<ELocation> memory, ulong ctime)
		{
			Contract.Requires(!bdf.IsAbstract && !bdf.IsBuiltin);

			//step into -> step, run->run other stays as is
			LocalFrame.DebugState dbgstate;
			if(this.CurrentCallFrame.DebuggerState == LocalFrame.DebugState.DebugStepInto)
				dbgstate = LocalFrame.DebugState.DebugStep;
			else
				dbgstate = ((LocalFrame.DebugState)Math.Min((int)LocalFrame.DebugState.DebugBreakAt, (int)this.CurrentCallFrame.DebuggerState));

			LocalFrame callee = new LocalFrame(bdf, isconsinvoke, dbgstate);
			callee.ClearVarsNotInScopeAddNew(bdf.Body.GetMethodEntryBlock().VarsInScopeAtEntry, bdf.Body.GetMethodEntryBlock().BlockVarsToTypeMap, memory, ctime, bdf.Body.T1T2Body.MinStmt());

			for(int i = bdf.Parameters.Count - 1; i >= 0; --i)
				callee.SimulateStoreToLocal(bdf.Parameters[i], memory, ctime, bdf.Body.T1T2Body.MinStmt());

			if(bdf.InvokeID.Sig.IsVirtualInvoke)
				callee.SimulateStoreToLocal(ExeProgram.TProg.NameTable.LookupLocalVarName("this"), memory, ctime, bdf.Body.T1T2Body.MinStmt());

			this.m_callstack.Add(callee);
		}

		/// <summary>
		/// Pop a call frame and set the return values as needed for exiting from a method.
		/// </summary>
		internal void ExitMethodCall()
		{
			LocalFrame calleeret = this.CurrentCallFrame;
			this.m_callstack.RemoveAt(this.m_callstack.Count - 1);

			calleeret.PopReturnValueIntoFramePropDebug(this.CurrentCallFrame);
		}

		/// <summary>
		/// Return the current active call frame.
		/// </summary>
		internal LocalFrame CurrentCallFrame
		{ get { return this.m_callstack[this.m_callstack.Count - 1]; } }

		/// <summary>
		/// Return the caller of the currently active method.
		/// </summary>
		internal LocalFrame CallerCallFrame
		{ get { return this.m_callstack[this.m_callstack.Count - 2]; } }

		////////
		//Helper for builtin

		private Random idgen = new Random(0);

		/// <summary>
		/// Simulate the given builtin call, this contains one of the result models, others are added to additionalOutResults
		/// </summary>
		internal void SimulateBuiltinCall(IRBodyDef bd, bool isconsinvoke, List<ELocation> memory, ulong ctime, long line)
		{
			String mns = bd.BuiltinSemanticName;
			IRType thisTy = bd.InvokeID.DefInClass; //for use on member methods

			int argc = (bd.InvokeID.Sig.IsVirtualInvoke ? (bd.InvokeID.Sig.InvokeSig.Count + 1) : bd.InvokeID.Sig.InvokeSig.Count);
			int baseoffset = this.CurrentCallFrame.GetFirstLocalStackPosForCall(argc);

			if(mns.Equals("object_init"))
				BuiltinSupport.BIInitializeObject(this.CurrentCallFrame, memory, ctime, line);
			else if(mns.Equals("init_keyvalpair"))
				BuiltinSupport.BIInitializeKeyValuePair((IRKeyValuePairType)thisTy, this.CurrentCallFrame, memory, ctime, line);
			else
			{
				DebugReport.ReportError("Unknown builtin method: " + mns);
				throw new InvalidOperationException();
			}
		}
	}
}
